Get Started
Lets’ load the packages we will need. tidyverse will
load all the packages we need
library(tidyverse)
library(readxl)
First attempt to read using the read_xlsx package -
which is developed specifically for xls and
xlsx files. Even though the function runs without error,
it’s highly recommended to View the contents of the data
frame.
raw <- read_xlsx("1918-2019election_results_by_pcon.xlsx")
View(raw)
There’s clearly some header information we need to deal with. The
skip argument allows some rows in the input file to be
ignored.
raw <- read_xlsx("1918-2019election_results_by_pcon.xlsx", skip = 3)
raw
And we need to select the particular sheet that we’re interested
in
excel_sheets("1918-2019election_results_by_pcon.xlsx")
raw <- read_xlsx("1918-2019election_results_by_pcon.xlsx", skip = 3,sheet = 29)
Lets use dplyr to pick columns of interest. Some manual renaming
involved :/
cleaned <- raw %>%
select(Constituency:Electorate, Turnout, `Total votes`, contains("Votes...")) %>%
rename("Conservative" = `Votes...8`,
"Labour" = `Votes...11`,
"Lib. Dem." = `Votes...14`,
"Brexit" = `Votes...17`,
"Green" = `Votes...20`,
"SNP" = `Votes...23`,
"Plaid Cymru" = `Votes...26`,
"DUP" = `Votes...29`,
"Sinn Fein" = `Votes...32`,
"SDLP" = `Votes...35`,
"UUP" = `Votes...38`,
"Alliance" = `Votes...41`,
"Other" = `Votes...44`)
cleaned
However, a bit of inspection shows some extra rows in the file that
shouldn’t have been included.
tail(cleaned)
Such problematic rows can be identified by having an NA
in the Country column
cleaned <- filter(cleaned, !is.na(Country))
We can save as csv file for future use.
write_csv(cleaned, "uk_election_2019.csv")
If at any point we need to read the original data we can do:-
cleaned <- read_csv("uk_election_2019.csv")
Rows: 650 Columns: 20
── Column specification ──────────────────────────────────────────────
Delimiter: ","
chr (4): Constituency, County, Country/Region, Country
dbl (16): Electorate, Turnout, Total votes, Conservative, Labour, ...
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
As part of understanding the data it is helpful to carry out a few
checks on the data quality. Although we have the total number of votes
as a column, we can check that this has been computed correctly.
cleaned %>%
mutate(Total_votes_calc = Electorate * Turnout) %>%
select(`Total votes`, Total_votes_calc)
We now have some data that we can start to visualise. For this we are
going to use the ggplot2 package. This is a
flexible and extendable plotting framework with a
consistent interface. It can make publication-ready figures with less
effort than “base” R. With some practice, you will spend less time
writing code to make figures and more time interpreting and
interrogating your data - which is what we really want to spend
our time doing!
A useful resource to guide us in making our figures is
data-to-viz. Sometimes it can even help to sketch a figure first using
pencil and paper.
We start by looking at the size of electorate size (the number of
people eligible to vote in each region). Since this is a single
continuous variable, a number of different plots are available to use.
We will use a histogram.
The different components required for a ggplot are:-
- the data frame that you want to plot
- definitions of how to plot variables / columns in that data to the
plot aesthetics (x-axis, y-axis, colour, shape etc)
- what type of plot (geom_) you want
ggplot(cleaned, aes(x = Electorate)) + geom_histogram()
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Changing the type of plot, for the same variable, can be achieved by
changing the geom_
ggplot(cleaned, aes(x = Electorate)) + geom_density()

ggplot(cleaned, aes(x = Electorate)) +geom_histogram(aes(y = ..density..)) + geom_density(col="blue")
Warning: The dot-dot notation (`..density..`) was deprecated in ggplot2 3.4.0.
ℹ Please use `after_stat(density)` instead.
This warning is displayed once every 8 hours.
Call `lifecycle::last_lifecycle_warnings()` to see where this warning
was generated.
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

N.B the gghistogram function from ggpubr
combined with stat_overlay_normal_density gives us a way to
overlay the standard normal distribution
gghistogram(cleaned, "Electorate",y = "..density..") + stat_overlay_normal_density(col="red",lty=2)
Warning: Using `bins = 30` by default. Pick better value with the argument `bins`.

Exercise
- Visualise the distribution of electorate sizes in different
countries?
- as a boxplot with jittered (geom_jitter) points overlaid

Lets see if the number of votes is related to the size of the
electorate (it should be!).
ggplot(cleaned, aes(x = Electorate, y = `Total votes`)) +
geom_point()

Since we can add different types of plot, it will be useful to add a
straight line.
ggplot(cleaned, aes(x = Electorate, y = `Total votes`)) +
geom_point() + geom_smooth(method = "lm")
`geom_smooth()` using formula = 'y ~ x'

The add-on package ggpubr allows us to add the
correlation value onto the graph. Other functions from this package
allow us to add p-values etc when comparing distributions via
boxplots.
library(ggpubr)
ggplot(cleaned, aes(x = Electorate, y = `Total votes`)) +
geom_point() + geom_smooth(method = "lm") + stat_cor()
`geom_smooth()` using formula = 'y ~ x'

Is this same trend observed for all Countries? One way to look at
this would be use different colours. This can be done by defining the
col aesthetic. It will assign colours in a sensible
fashion, and we can also define our own palette.
ggplot(cleaned, aes(x = Electorate, y = `Total votes`, col = Country)) +
geom_point()

Colours can be defined using the scale_color_manual
function. Here we make use of the RColorBrewer package for
a pre-defined set. However, in the following code the contents of
my_colours could be any combination of colour names or
rgb.
library(RColorBrewer)
my_colours <- brewer.pal(4, "Set1")
my_colours
[1] "#E41A1C" "#377EB8" "#4DAF4A" "#984EA3"
ggplot(cleaned, aes(x = Electorate, y = `Total votes`, col = Country)) +
geom_point() + scale_color_manual(values=my_colours)

A useful plotting technique is to split a figure depending on a
variable. Here, we can plot the size of electorate against total votes
for each country
ggplot(cleaned, aes(x = Electorate, y = `Total votes`)) +
geom_point() + geom_smooth(method = "lm") + stat_cor() + facet_wrap(~Country)
`geom_smooth()` using formula = 'y ~ x'

We can compare numbers of votes between different regions. Let’s look
at Sheffield
shef_data <- filter(cleaned, grepl("SHEFF",Constituency))
ggplot(shef_data, aes(x = Constituency, y = Conservative)) + geom_col(fill="#0087DC")

But how many votes did the parties get overall, and who won? We can
start by adding up the respective columns. The
summarize/summarise collection of functions
allow many ways to summarise our data. The general approach is to use
specify what summary function you want to use on which columns, and say
what you want the result to be called:-
## na.rm needed as there are some 'NA' values which would result in 'NA' for the sum
summarise(cleaned,
Conservative_Votes = sum(Conservative,na.rm = TRUE))
Typically the summary functions are used for descriptive statistics;
)mean, median, sd,
var etc) and give a single value. However,
we don’t want to spend our time typing out code to summarise all the
colums we are interested in
## Don't do this - unless you have very few columns
summarise(cleaned,
Conservative_Votes = sum(Conservative,na.rm = TRUE),
Labour_Votes = sum(Labour, na.rm = TRUE),
Lib_Dem_Votes = sum(`Lib. Dem.`,na.rm=TRUE)
)
dplyr is replete with shortcuts that are designed to
make your life easier.
summarize_at(cleaned, vars(Conservative:Alliance),sum, na.rm = TRUE)
But what happens if we try and visualise? Try and write the
ggplot code.
## don't try and run this - it won't work!
cleaned %>%
summarize_at(vars(Conservative:Alliance),sum, na.rm = TRUE) %>%
ggplot(aes(x = , y = )) + geom_col()
We can’t do this at the moment, because the data are “wide” rather
than “long”. To make the data long, we can specify a set of paired
key:value columns. In our case, we want a column that gives a number of
votes, and a column to tell us which party those votes were for.
vote_sums <- summarize_at(cleaned, vars(Conservative:Alliance),sum, na.rm = TRUE)
pivot_longer(vote_sums, everything(), values_to = "Votes", names_to = "Party")
Let’s talk about “piping”
A relatively recent addition to the R language is the concept of
piping, which can help us to make code easier to write (and read). The
premise is that one line of code is used as the input for the following
line. This is especially effective in dplyr, where must functions take a
data frame as an input and return a data frame as an output. When
looking at code online, you will see many examples of code written in
this fashion. The %>% symbols at the end of a line are a
giveaway. The shortcut CTRL + SHIFT + M is used to print this.
total_votes <-
cleaned %>%
summarize_at(vars(Conservative:Other),sum, na.rm = TRUE) %>%
pivot_longer(everything(),values_to = "Votes", names_to = "Party") %>%
mutate(Party = fct_reorder(Party, Votes))
You can also “pipe” between dplyr and ggplot2 code
total_votes %>%
ggplot(aes(x = Party, y = Votes, fill=Party)) + geom_col()

However, these summed values don’t acutally tell us who one the
election. Instead, we need to know who won in each constituency.
cleaned %>%
pivot_longer(Conservative:Other, names_to = "Party", values_to ="Votes") %>%
filter(!is.na(Votes))
NA
Calculate the percentage of votes for each party in each
constituency.
cleaned %>%
pivot_longer(Conservative:Other, names_to = "Party", values_to ="Votes") %>%
filter(!is.na(Votes)) %>%
mutate(`Percentage of Vote` = (Votes / `Total votes`)*100)
We can now drill-down into the results for particular constituencies.
Lets’ look at all the Sheffield results for the four biggest
parties.
shef_votes <- cleaned %>%
pivot_longer(Conservative:Other, names_to = "Party", values_to ="Votes") %>%
filter(!is.na(Votes)) %>%
mutate(`Percentage of Vote` = (Votes / `Total votes`)*100) %>%
filter(grepl("SHEFFIELD", Constituency),
Party %in% c("Conservative", "Green", "Labour", "Lib. Dem."))
Exercise
Produce the following graph to show the vote split for the different
parts of Sheffield. These colours were used in the example plot:-
c("#0087DC", "#008066","#DC241f","#FDBB30")

Now to determine the winners for each constituency. To do this, we
will order the percentage of votes within each constituency by first
grouping within constituency and then using arrange.
cleaned %>%
pivot_longer(Conservative:Other, names_to = "Party", values_to ="Votes") %>%
filter(!is.na(Votes)) %>%
mutate(`Percentage of Vote` = (Votes / `Total votes`)*100) %>%
group_by(Constituency) %>%
arrange(desc(`Percentage of Vote`),.by_group = TRUE)
To calculate the majority (i.e. how much each seat was won by), we’ll
need the first and second place votes. The lead function is
used to find the second highest number of votes.
cleaned %>%
pivot_longer(Conservative:Other, names_to = "Party", values_to ="Votes") %>%
filter(!is.na(Votes)) %>%
mutate(`Percentage of Vote` = (Votes / `Total votes`)*100) %>%
group_by(Constituency) %>%
arrange(desc(`Percentage of Vote`),.by_group = TRUE) %>%
slice(1:2) %>%
mutate(`Second Place` = lead(`Percentage of Vote`,order_by = Constituency), Majority = `Percentage of Vote` - `Second Place`)
Finally, we can select the winner of each seat and their
majority.
results <- cleaned %>%
pivot_longer(Conservative:Other, names_to = "Party", values_to ="Votes") %>%
filter(!is.na(Votes)) %>%
mutate(`Percentage of Vote` = (Votes / `Total votes`)*100) %>%
group_by(Constituency) %>%
arrange(desc(`Percentage of Vote`),.by_group = TRUE) %>%
slice(1:2) %>%
mutate(`Second Place` = lead(`Percentage of Vote`,order_by = Constituency), Majority = `Percentage of Vote` - `Second Place`) %>%
slice(1)
Did some parties win by slimmer majorities?
results %>%
filter(Party %in% c("Conservative", "Green", "Labour", "Lib. Dem.","SNP")) %>%
ggplot(aes(x = Party, y = Majority,fill=Party)) + geom_boxplot() + geom_jitter(width=0.1) + scale_fill_manual(values=c("#0087DC", "#008066","#DC241f","#FDBB30","#FFFF00"))

We can now work out the number of seats
seats_2019 %>%
filter(Party %in% c("Conservative", "Green", "Labour", "Lib. Dem.","SNP")) %>%
ggplot(aes(x = fct_reorder(Party, Seats),y = Seats,fill=Party)) + geom_col() + scale_fill_manual(values=c("#0087DC", "#008066","#DC241f","#FDBB30","#FFFF00"))

LS0tCnRpdGxlOiAiUmV2aWV3IG9mIERhdGEgTWFuaXB1bGF0aW9uIGFuZCBWaXN1YWxpemF0aW9uIHVzaW5nIHRpZHl2ZXJzZSIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKIyBPdXRsaW5lCgpJbiB0aGlzIHR1dG9yaWFsIHdlIHdpbGwgaW50cm9kdWN0aW9uIC8gcmV2aWV3IHNvbWUgY29uY2VwdHMgb2YgdXNpbmcgYGRwbHlyYCwgYGdncGxvdDJgIGFuZCBvdGhlciBwYXJ0cyBvZiB0aGUgInRpZHl2ZXJzZSIuIEJyaWVmbHk6LQoKLSBEYXRhIEltcG9ydAogIC0gRnJvbSBYbHN4IGFuZCBjc3YgZm9ybWF0cwotIERhdGEgQ2xlYW5pbmcKICAtIFJlbmFtaW5nIGNvbHVtbnMsIGZpbHRlcmluZywgdHJhbnNmb3JtaW5nIGNvbHVtbnMKLSBEYXRhIE1hbmlwdWxhdGlvbgogIC0gIldpZGUiIHZzICJMb25nIiBkYXRhCiAgLSAoUmUtKWFycmFuZ2luZyByb3dzCiAgLSBHcm91cGluZyBhbmQgc3VtbWFyaXNpbmcgdGFibGVzCi0gRGF0YSBWaXN1YWxpc2F0aW9uCiAgLSBCYXNpYyBwbG90IHR5cGVzCiAgLSBTcGxpdHRpbmctdXAgcGxvdHMgYmFzZWQgb24gdmFyaWFibGVzIC0gZmFjZXRpbmcKICAtIFVzaW5nIGNvbG91cnMKCgpGb3IgbW9yZSBpbmZvcm1hdGlvbiBvbiB0aGVzZSBrZXkgcGFja2FnZXMgeW91IGNhbiBjb25zdWx0IHRoZSAiY2hlYXRzaGVldHMiIGF2YWlsYWJsZSB0aHJvdWdoIHRoZSBoZWxwIG1lbnVzLCBvciBmcm9tIHRoZSBbdGlkeXZlcnNlXShodHRwczovL3d3dy50aWR5dmVyc2Uub3JnLykgd2Vic2l0ZSBpdHNlbGYuCgojIEdldCBTdGFydGVkCgpMZXRzJyBsb2FkIHRoZSBwYWNrYWdlcyB3ZSB3aWxsIG5lZWQuIGB0aWR5dmVyc2VgIHdpbGwgbG9hZCBhbGwgdGhlIHBhY2thZ2VzIHdlIG5lZWQKCmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShyZWFkeGwpCmBgYAoKCkZpcnN0IGF0dGVtcHQgdG8gcmVhZCB1c2luZyB0aGUgYHJlYWRfeGxzeGAgcGFja2FnZSAtIHdoaWNoIGlzIGRldmVsb3BlZCBzcGVjaWZpY2FsbHkgZm9yIGB4bHNgIGFuZCBgeGxzeGAgZmlsZXMuIEV2ZW4gdGhvdWdoIHRoZSBmdW5jdGlvbiBydW5zIHdpdGhvdXQgZXJyb3IsIGl0J3MgaGlnaGx5IHJlY29tbWVuZGVkIHRvIGBWaWV3YCB0aGUgY29udGVudHMgb2YgdGhlIGRhdGEgZnJhbWUuCgpgYGB7ciBtZXNzYWdlPUZBTFNFfQpyYXcgPC0gcmVhZF94bHN4KCIxOTE4LTIwMTllbGVjdGlvbl9yZXN1bHRzX2J5X3Bjb24ueGxzeCIpClZpZXcocmF3KQpgYGAKClRoZXJlJ3MgY2xlYXJseSBzb21lIGhlYWRlciBpbmZvcm1hdGlvbiB3ZSBuZWVkIHRvIGRlYWwgd2l0aC4gVGhlIGBza2lwYCBhcmd1bWVudCBhbGxvd3Mgc29tZSByb3dzIGluIHRoZSBpbnB1dCBmaWxlIHRvIGJlIGlnbm9yZWQuIAoKYGBge3IgbWVzc2FnZT1GQUxTRX0KcmF3IDwtIHJlYWRfeGxzeCgiMTkxOC0yMDE5ZWxlY3Rpb25fcmVzdWx0c19ieV9wY29uLnhsc3giLCBza2lwID0gMykKcmF3CmBgYAoKQW5kIHdlIG5lZWQgdG8gc2VsZWN0IHRoZSBwYXJ0aWN1bGFyIHNoZWV0IHRoYXQgd2UncmUgaW50ZXJlc3RlZCBpbiAKCmBgYHtyfQpleGNlbF9zaGVldHMoIjE5MTgtMjAxOWVsZWN0aW9uX3Jlc3VsdHNfYnlfcGNvbi54bHN4IikKcmF3IDwtIHJlYWRfeGxzeCgiMTkxOC0yMDE5ZWxlY3Rpb25fcmVzdWx0c19ieV9wY29uLnhsc3giLCBza2lwID0gMyxzaGVldCA9IDI5KQpgYGAKCkxldHMgdXNlIGRwbHlyIHRvIHBpY2sgY29sdW1ucyBvZiBpbnRlcmVzdC4gU29tZSBtYW51YWwgcmVuYW1pbmcgaW52b2x2ZWQgOi8KCmBgYHtyfQpjbGVhbmVkIDwtIHJhdyAlPiUgCiAgc2VsZWN0KENvbnN0aXR1ZW5jeTpFbGVjdG9yYXRlLCBUdXJub3V0LCBgVG90YWwgdm90ZXNgLCBjb250YWlucygiVm90ZXMuLi4iKSkgJT4lIAogIHJlbmFtZSgiQ29uc2VydmF0aXZlIiA9IGBWb3Rlcy4uLjhgLAogICAgICAgICAiTGFib3VyIiA9IGBWb3Rlcy4uLjExYCwKICAgICAgICAgIkxpYi4gRGVtLiIgPSBgVm90ZXMuLi4xNGAsCiAgICAgICAgICJCcmV4aXQiID0gYFZvdGVzLi4uMTdgLAogICAgICAgICAiR3JlZW4iID0gYFZvdGVzLi4uMjBgLAogICAgICAgICAiU05QIiA9IGBWb3Rlcy4uLjIzYCwKICAgICAgICAgIlBsYWlkIEN5bXJ1IiA9IGBWb3Rlcy4uLjI2YCwKICAgICAgICAgIkRVUCIgPSBgVm90ZXMuLi4yOWAsCiAgICAgICAgICJTaW5uIEZlaW4iID0gYFZvdGVzLi4uMzJgLAogICAgICAgICAiU0RMUCIgPSBgVm90ZXMuLi4zNWAsCiAgICAgICAgICJVVVAiID0gYFZvdGVzLi4uMzhgLAogICAgICAgICAiQWxsaWFuY2UiID0gYFZvdGVzLi4uNDFgLAogICAgICAgICAiT3RoZXIiID0gYFZvdGVzLi4uNDRgKQpjbGVhbmVkCmBgYApIb3dldmVyLCBhIGJpdCBvZiBpbnNwZWN0aW9uIHNob3dzIHNvbWUgZXh0cmEgcm93cyBpbiB0aGUgZmlsZSB0aGF0IHNob3VsZG4ndCBoYXZlIGJlZW4gaW5jbHVkZWQuCgpgYGB7cn0KdGFpbChjbGVhbmVkKQpgYGAKClN1Y2ggcHJvYmxlbWF0aWMgcm93cyBjYW4gYmUgaWRlbnRpZmllZCBieSBoYXZpbmcgYW4gYE5BYCBpbiB0aGUgYENvdW50cnlgIGNvbHVtbgoKYGBge3J9CmNsZWFuZWQgPC0gZmlsdGVyKGNsZWFuZWQsICFpcy5uYShDb3VudHJ5KSkKYGBgCgoKV2UgY2FuIHNhdmUgYXMgY3N2IGZpbGUgZm9yIGZ1dHVyZSB1c2UuCgpgYGB7cn0Kd3JpdGVfY3N2KGNsZWFuZWQsICJ1a19lbGVjdGlvbl8yMDE5LmNzdiIpCmBgYAoKSWYgYXQgYW55IHBvaW50IHdlIG5lZWQgdG8gcmVhZCB0aGUgb3JpZ2luYWwgZGF0YSB3ZSBjYW4gZG86LQoKYGBge3J9CmNsZWFuZWQgPC0gcmVhZF9jc3YoInVrX2VsZWN0aW9uXzIwMTkuY3N2IikKYGBgCgpBcyBwYXJ0IG9mIHVuZGVyc3RhbmRpbmcgdGhlIGRhdGEgaXQgaXMgaGVscGZ1bCB0byBjYXJyeSBvdXQgYSBmZXcgY2hlY2tzIG9uIHRoZSBkYXRhIHF1YWxpdHkuIEFsdGhvdWdoIHdlIGhhdmUgdGhlIHRvdGFsIG51bWJlciBvZiB2b3RlcyBhcyBhIGNvbHVtbiwgd2UgY2FuIGNoZWNrIHRoYXQgdGhpcyBoYXMgYmVlbiBjb21wdXRlZCBjb3JyZWN0bHkuIAoKYGBge3J9CmNsZWFuZWQgJT4lIAogIG11dGF0ZShUb3RhbF92b3Rlc19jYWxjID0gRWxlY3RvcmF0ZSAqIFR1cm5vdXQpICU+JQogIHNlbGVjdChgVG90YWwgdm90ZXNgLCBUb3RhbF92b3Rlc19jYWxjKQpgYGAKCldlIG5vdyBoYXZlIHNvbWUgZGF0YSB0aGF0IHdlIGNhbiBzdGFydCB0byB2aXN1YWxpc2UuIEZvciB0aGlzIHdlIGFyZSBnb2luZyB0byB1c2UgdGhlIGBnZ3Bsb3QyYCBwYWNrYWdlLiBUaGlzIGlzIGEgKmZsZXhpYmxlKiBhbmQgKmV4dGVuZGFibGUqIHBsb3R0aW5nIGZyYW1ld29yayB3aXRoIGEgY29uc2lzdGVudCBpbnRlcmZhY2UuIEl0IGNhbiBtYWtlIHB1YmxpY2F0aW9uLXJlYWR5IGZpZ3VyZXMgd2l0aCBsZXNzIGVmZm9ydCB0aGFuICJiYXNlIiBSLiBXaXRoIHNvbWUgcHJhY3RpY2UsIHlvdSB3aWxsIHNwZW5kIGxlc3MgdGltZSB3cml0aW5nIGNvZGUgdG8gbWFrZSBmaWd1cmVzIGFuZCAqKm1vcmUgdGltZSBpbnRlcnByZXRpbmcgYW5kIGludGVycm9nYXRpbmcgeW91ciBkYXRhKiogLSB3aGljaCBpcyB3aGF0IHdlIHJlYWxseSB3YW50IHRvIHNwZW5kIG91ciB0aW1lIGRvaW5nIQoKQSB1c2VmdWwgcmVzb3VyY2UgdG8gZ3VpZGUgdXMgaW4gbWFraW5nIG91ciBmaWd1cmVzIGlzIGRhdGEtdG8tdml6LiBTb21ldGltZXMgaXQgY2FuIGV2ZW4gaGVscCB0byBza2V0Y2ggYSBmaWd1cmUgZmlyc3QgdXNpbmcgcGVuY2lsIGFuZCBwYXBlci4KCi0gW0Nob29zZSB0aGUgbW9zdCBhcHByb3ByaWF0ZSBmaWd1cmUgZm9yIHlvdXIgZGF0YSAtIGRhdGEtdG8tdml6XShodHRwczovL3d3dy5kYXRhLXRvLXZpei5jb20vKQoKV2Ugc3RhcnQgYnkgbG9va2luZyBhdCB0aGUgc2l6ZSBvZiBlbGVjdG9yYXRlIHNpemUgKHRoZSBudW1iZXIgb2YgcGVvcGxlIGVsaWdpYmxlIHRvIHZvdGUgaW4gZWFjaCByZWdpb24pLiBTaW5jZSB0aGlzIGlzIGEgc2luZ2xlIGNvbnRpbnVvdXMgdmFyaWFibGUsIGEgbnVtYmVyIG9mIGRpZmZlcmVudCBwbG90cyBhcmUgYXZhaWxhYmxlIHRvIHVzZS4gV2Ugd2lsbCB1c2UgYSBoaXN0b2dyYW0uCgpUaGUgZGlmZmVyZW50IGNvbXBvbmVudHMgcmVxdWlyZWQgZm9yIGEgZ2dwbG90IGFyZTotCgotIHRoZSBkYXRhIGZyYW1lIHRoYXQgeW91IHdhbnQgdG8gcGxvdAotIGRlZmluaXRpb25zIG9mIGhvdyB0byBwbG90IHZhcmlhYmxlcyAvIGNvbHVtbnMgaW4gdGhhdCBkYXRhIHRvIHRoZSBwbG90IGFlc3RoZXRpY3MgKHgtYXhpcywgeS1heGlzLCBjb2xvdXIsIHNoYXBlIGV0YykKLSB3aGF0IHR5cGUgb2YgcGxvdCAoZ2VvbV8pIHlvdSB3YW50CgoKCmBgYHtyfQpnZ3Bsb3QoY2xlYW5lZCwgYWVzKHggPSBFbGVjdG9yYXRlKSkgKyBnZW9tX2hpc3RvZ3JhbSgpCmBgYAoKQ2hhbmdpbmcgdGhlIHR5cGUgb2YgcGxvdCwgZm9yIHRoZSBzYW1lIHZhcmlhYmxlLCBjYW4gYmUgYWNoaWV2ZWQgYnkgY2hhbmdpbmcgdGhlIGBnZW9tX2AKCmBgYHtyfQpnZ3Bsb3QoY2xlYW5lZCwgYWVzKHggPSBFbGVjdG9yYXRlKSkgKyBnZW9tX2RlbnNpdHkoKQpgYGAKYGBge3J9CmdncGxvdChjbGVhbmVkLCBhZXMoeCA9IEVsZWN0b3JhdGUpKSArZ2VvbV9oaXN0b2dyYW0oYWVzKHkgPSAuLmRlbnNpdHkuLikpICsgZ2VvbV9kZW5zaXR5KGNvbD0iYmx1ZSIpCmBgYAoKTi5CIHRoZSBgZ2doaXN0b2dyYW1gIGZ1bmN0aW9uIGZyb20gYGdncHVicmAgY29tYmluZWQgd2l0aCBgc3RhdF9vdmVybGF5X25vcm1hbF9kZW5zaXR5YCBnaXZlcyB1cyBhIHdheSB0byBvdmVybGF5IHRoZSAqc3RhbmRhcmQqIG5vcm1hbCBkaXN0cmlidXRpb24KCmBgYHtyfQpnZ2hpc3RvZ3JhbShjbGVhbmVkLCAiRWxlY3RvcmF0ZSIseSA9ICIuLmRlbnNpdHkuLiIpICsgc3RhdF9vdmVybGF5X25vcm1hbF9kZW5zaXR5KGNvbD0icmVkIixsdHk9MikKYGBgCgoKLS0tCgoqKkV4ZXJjaXNlKioKCi0gVmlzdWFsaXNlIHRoZSBkaXN0cmlidXRpb24gb2YgZWxlY3RvcmF0ZSBzaXplcyBpbiBkaWZmZXJlbnQgY291bnRyaWVzPwogIC0gYXMgYSBib3hwbG90IHdpdGggaml0dGVyZWQgKGdlb21faml0dGVyKSBwb2ludHMgb3ZlcmxhaWQKICAKIVtdKGVsZWN0b3JhdGUtc2l6ZS1jb21wYXJpc29uLnBuZykgIAoKLS0tCgpMZXRzIHNlZSBpZiB0aGUgbnVtYmVyIG9mIHZvdGVzIGlzIHJlbGF0ZWQgdG8gdGhlIHNpemUgb2YgdGhlIGVsZWN0b3JhdGUgKGl0IHNob3VsZCBiZSEpLiAKCgpgYGB7cn0KZ2dwbG90KGNsZWFuZWQsIGFlcyh4ID0gRWxlY3RvcmF0ZSwgeSA9IGBUb3RhbCB2b3Rlc2ApKSArCiAgZ2VvbV9wb2ludCgpCmBgYApTaW5jZSB3ZSBjYW4gYWRkIGRpZmZlcmVudCB0eXBlcyBvZiBwbG90LCBpdCB3aWxsIGJlIHVzZWZ1bCB0byBhZGQgYSBzdHJhaWdodCBsaW5lLgoKYGBge3J9CmdncGxvdChjbGVhbmVkLCBhZXMoeCA9IEVsZWN0b3JhdGUsIHkgPSBgVG90YWwgdm90ZXNgKSkgKwogIGdlb21fcG9pbnQoKSArIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIpCmBgYAoKVGhlIGFkZC1vbiBwYWNrYWdlIGBnZ3B1YnJgIGFsbG93cyB1cyB0byBhZGQgdGhlIGNvcnJlbGF0aW9uIHZhbHVlIG9udG8gdGhlIGdyYXBoLiBPdGhlciBmdW5jdGlvbnMgZnJvbSB0aGlzIHBhY2thZ2UgYWxsb3cgdXMgdG8gYWRkIHAtdmFsdWVzIGV0YyB3aGVuIGNvbXBhcmluZyBkaXN0cmlidXRpb25zIHZpYSBib3hwbG90cy4KCmBgYHtyfQpsaWJyYXJ5KGdncHVicikKZ2dwbG90KGNsZWFuZWQsIGFlcyh4ID0gRWxlY3RvcmF0ZSwgeSA9IGBUb3RhbCB2b3Rlc2ApKSArCiAgZ2VvbV9wb2ludCgpICsgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIikgKyBzdGF0X2NvcigpCmBgYAoKSXMgdGhpcyBzYW1lIHRyZW5kIG9ic2VydmVkIGZvciBhbGwgQ291bnRyaWVzPyBPbmUgd2F5IHRvIGxvb2sgYXQgdGhpcyB3b3VsZCBiZSB1c2UgZGlmZmVyZW50IGNvbG91cnMuIFRoaXMgY2FuIGJlIGRvbmUgYnkgZGVmaW5pbmcgdGhlIGBjb2xgIGFlc3RoZXRpYy4gSXQgd2lsbCBhc3NpZ24gY29sb3VycyBpbiBhIHNlbnNpYmxlIGZhc2hpb24sIGFuZCB3ZSBjYW4gYWxzbyBkZWZpbmUgb3VyIG93biBwYWxldHRlLgoKCgpgYGB7cn0KZ2dwbG90KGNsZWFuZWQsIGFlcyh4ID0gRWxlY3RvcmF0ZSwgeSA9IGBUb3RhbCB2b3Rlc2AsIGNvbCA9IENvdW50cnkpKSArCiAgZ2VvbV9wb2ludCgpCmBgYApDb2xvdXJzIGNhbiBiZSBkZWZpbmVkIHVzaW5nIHRoZSBgc2NhbGVfY29sb3JfbWFudWFsYCBmdW5jdGlvbi4gSGVyZSB3ZSBtYWtlIHVzZSBvZiB0aGUgYFJDb2xvckJyZXdlcmAgcGFja2FnZSBmb3IgYSBwcmUtZGVmaW5lZCBzZXQuIEhvd2V2ZXIsIGluIHRoZSBmb2xsb3dpbmcgY29kZSB0aGUgY29udGVudHMgb2YgYG15X2NvbG91cnNgIGNvdWxkIGJlIGFueSBjb21iaW5hdGlvbiBvZiBjb2xvdXIgbmFtZXMgb3IgcmdiLgoKYGBge3J9CmxpYnJhcnkoUkNvbG9yQnJld2VyKQpteV9jb2xvdXJzIDwtIGJyZXdlci5wYWwoNCwgIlNldDEiKQpteV9jb2xvdXJzCmdncGxvdChjbGVhbmVkLCBhZXMoeCA9IEVsZWN0b3JhdGUsIHkgPSBgVG90YWwgdm90ZXNgLCBjb2wgPSBDb3VudHJ5KSkgKwogIGdlb21fcG9pbnQoKSArIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9bXlfY29sb3VycykKYGBgCgoKQSB1c2VmdWwgcGxvdHRpbmcgdGVjaG5pcXVlIGlzIHRvIHNwbGl0IGEgZmlndXJlIGRlcGVuZGluZyBvbiBhIHZhcmlhYmxlLiBIZXJlLCB3ZSBjYW4gcGxvdCB0aGUgc2l6ZSBvZiBlbGVjdG9yYXRlIGFnYWluc3QgdG90YWwgdm90ZXMgZm9yIGVhY2ggY291bnRyeQoKYGBge3J9CmdncGxvdChjbGVhbmVkLCBhZXMoeCA9IEVsZWN0b3JhdGUsIHkgPSBgVG90YWwgdm90ZXNgKSkgKwogIGdlb21fcG9pbnQoKSArIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIpICsgc3RhdF9jb3IoKSArIGZhY2V0X3dyYXAofkNvdW50cnkpCmBgYAoKV2UgY2FuIGNvbXBhcmUgbnVtYmVycyBvZiB2b3RlcyBiZXR3ZWVuIGRpZmZlcmVudCByZWdpb25zLiBMZXQncyBsb29rIGF0IFNoZWZmaWVsZAoKYGBge3J9CnNoZWZfZGF0YSA8LSBmaWx0ZXIoY2xlYW5lZCwgZ3JlcGwoIlNIRUZGIixDb25zdGl0dWVuY3kpKQpgYGAKYGBge3J9CmdncGxvdChzaGVmX2RhdGEsIGFlcyh4ID0gQ29uc3RpdHVlbmN5LCB5ID0gQ29uc2VydmF0aXZlKSkgKyBnZW9tX2NvbChmaWxsPSIjMDA4N0RDIikKYGBgCgpCdXQgaG93IG1hbnkgdm90ZXMgZGlkIHRoZSBwYXJ0aWVzIGdldCBvdmVyYWxsLCBhbmQgd2hvIHdvbj8gV2UgY2FuIHN0YXJ0IGJ5IGFkZGluZyB1cCB0aGUgcmVzcGVjdGl2ZSBjb2x1bW5zLiBUaGUgYHN1bW1hcml6ZWAvYHN1bW1hcmlzZWAgY29sbGVjdGlvbiBvZiBmdW5jdGlvbnMgYWxsb3cgbWFueSB3YXlzIHRvIHN1bW1hcmlzZSBvdXIgZGF0YS4gVGhlIGdlbmVyYWwgYXBwcm9hY2ggaXMgdG8gdXNlIHNwZWNpZnkgd2hhdCBzdW1tYXJ5IGZ1bmN0aW9uIHlvdSB3YW50IHRvIHVzZSBvbiB3aGljaCBjb2x1bW5zLCBhbmQgc2F5IHdoYXQgeW91IHdhbnQgdGhlIHJlc3VsdCB0byBiZSBjYWxsZWQ6LQoKYGBge3J9CiMjIG5hLnJtIG5lZWRlZCBhcyB0aGVyZSBhcmUgc29tZSAnTkEnIHZhbHVlcyB3aGljaCB3b3VsZCByZXN1bHQgaW4gJ05BJyBmb3IgdGhlIHN1bQoKc3VtbWFyaXNlKGNsZWFuZWQsIAogICAgICAgICAgQ29uc2VydmF0aXZlX1ZvdGVzID0gc3VtKENvbnNlcnZhdGl2ZSxuYS5ybSA9IFRSVUUpKQpgYGAKVHlwaWNhbGx5IHRoZSBzdW1tYXJ5IGZ1bmN0aW9ucyBhcmUgdXNlZCBmb3IgZGVzY3JpcHRpdmUgc3RhdGlzdGljczsgKWBtZWFuYCwgYG1lZGlhbmAsIGBzZGAsIGB2YXJgIGV0YykgYW5kIGdpdmUgYSAqKnNpbmdsZSB2YWx1ZSoqLiBIb3dldmVyLCB3ZSBkb24ndCB3YW50IHRvIHNwZW5kIG91ciB0aW1lIHR5cGluZyBvdXQgY29kZSB0byBzdW1tYXJpc2UgYWxsIHRoZSBjb2x1bXMgd2UgYXJlIGludGVyZXN0ZWQgaW4KCmBgYHtyfQojIyBEb24ndCBkbyB0aGlzIC0gdW5sZXNzIHlvdSBoYXZlIHZlcnkgZmV3IGNvbHVtbnMKCnN1bW1hcmlzZShjbGVhbmVkLCAKICAgICAgICAgIENvbnNlcnZhdGl2ZV9Wb3RlcyA9IHN1bShDb25zZXJ2YXRpdmUsbmEucm0gPSBUUlVFKSwKICAgICAgICAgIExhYm91cl9Wb3RlcyA9IHN1bShMYWJvdXIsIG5hLnJtID0gVFJVRSksCiAgICAgICAgICBMaWJfRGVtX1ZvdGVzID0gc3VtKGBMaWIuIERlbS5gLG5hLnJtPVRSVUUpCiAgICAgICAgICApCmBgYApgZHBseXJgIGlzIHJlcGxldGUgd2l0aCBzaG9ydGN1dHMgdGhhdCBhcmUgZGVzaWduZWQgdG8gbWFrZSB5b3VyIGxpZmUgZWFzaWVyLiAKCmBgYHtyfQpzdW1tYXJpemVfYXQoY2xlYW5lZCwgdmFycyhDb25zZXJ2YXRpdmU6QWxsaWFuY2UpLHN1bSwgbmEucm0gPSBUUlVFKQpgYGAKQnV0IHdoYXQgaGFwcGVucyBpZiB3ZSB0cnkgYW5kIHZpc3VhbGlzZT8gVHJ5IGFuZCB3cml0ZSB0aGUgYGdncGxvdGAgY29kZS4KCmBgYHtyIGV2YWw9RkFMU0V9CiMjIGRvbid0IHRyeSBhbmQgcnVuIHRoaXMgLSBpdCB3b24ndCB3b3JrIQpjbGVhbmVkICU+JSAKICBzdW1tYXJpemVfYXQodmFycyhDb25zZXJ2YXRpdmU6QWxsaWFuY2UpLHN1bSwgbmEucm0gPSBUUlVFKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gLCB5ID0gKSkgKyBnZW9tX2NvbCgpCmBgYAoKV2UgY2FuJ3QgZG8gdGhpcyBhdCB0aGUgbW9tZW50LCBiZWNhdXNlIHRoZSBkYXRhIGFyZSAid2lkZSIgcmF0aGVyIHRoYW4gImxvbmciLiBUbyBtYWtlIHRoZSBkYXRhIGxvbmcsIHdlIGNhbiBzcGVjaWZ5IGEgc2V0IG9mIHBhaXJlZCBrZXk6dmFsdWUgY29sdW1ucy4gSW4gb3VyIGNhc2UsIHdlIHdhbnQgYSBjb2x1bW4gdGhhdCBnaXZlcyBhIG51bWJlciBvZiB2b3RlcywgYW5kIGEgY29sdW1uIHRvIHRlbGwgdXMgd2hpY2ggcGFydHkgdGhvc2Ugdm90ZXMgd2VyZSBmb3IuCgoKYGBge3J9CnZvdGVfc3VtcyA8LSBzdW1tYXJpemVfYXQoY2xlYW5lZCwgdmFycyhDb25zZXJ2YXRpdmU6QWxsaWFuY2UpLHN1bSwgbmEucm0gPSBUUlVFKQpwaXZvdF9sb25nZXIodm90ZV9zdW1zLCBldmVyeXRoaW5nKCksIHZhbHVlc190byA9ICJWb3RlcyIsIG5hbWVzX3RvID0gIlBhcnR5IikKYGBgCgojIyBMZXQncyB0YWxrIGFib3V0ICJwaXBpbmciCgpBIHJlbGF0aXZlbHkgcmVjZW50IGFkZGl0aW9uIHRvIHRoZSBSIGxhbmd1YWdlIGlzIHRoZSBjb25jZXB0IG9mIHBpcGluZywgd2hpY2ggY2FuIGhlbHAgdXMgdG8gbWFrZSBjb2RlIGVhc2llciB0byB3cml0ZSAoYW5kIHJlYWQpLiBUaGUgcHJlbWlzZSBpcyB0aGF0IG9uZSBsaW5lIG9mIGNvZGUgaXMgdXNlZCBhcyB0aGUgaW5wdXQgZm9yIHRoZSBmb2xsb3dpbmcgbGluZS4gVGhpcyBpcyBlc3BlY2lhbGx5IGVmZmVjdGl2ZSBpbiBkcGx5ciwgd2hlcmUgbXVzdCBmdW5jdGlvbnMgdGFrZSBhIGRhdGEgZnJhbWUgYXMgYW4gaW5wdXQgYW5kIHJldHVybiBhIGRhdGEgZnJhbWUgYXMgYW4gb3V0cHV0LiBXaGVuIGxvb2tpbmcgYXQgY29kZSBvbmxpbmUsIHlvdSB3aWxsIHNlZSBtYW55IGV4YW1wbGVzIG9mIGNvZGUgd3JpdHRlbiBpbiB0aGlzIGZhc2hpb24uIFRoZSBgJT4lYCBzeW1ib2xzIGF0IHRoZSBlbmQgb2YgYSBsaW5lIGFyZSBhIGdpdmVhd2F5LiBUaGUgc2hvcnRjdXQgQ1RSTCArIFNISUZUICsgTSBpcyB1c2VkIHRvIHByaW50IHRoaXMuCgpgYGB7cn0KdG90YWxfdm90ZXMgPC0gCmNsZWFuZWQgJT4lIAogIHN1bW1hcml6ZV9hdCh2YXJzKENvbnNlcnZhdGl2ZTpPdGhlciksc3VtLCBuYS5ybSA9IFRSVUUpICU+JSAKICBwaXZvdF9sb25nZXIoZXZlcnl0aGluZygpLHZhbHVlc190byA9ICJWb3RlcyIsIG5hbWVzX3RvID0gIlBhcnR5IikgJT4lIAogIG11dGF0ZShQYXJ0eSA9IGZjdF9yZW9yZGVyKFBhcnR5LCBWb3RlcykpCmBgYAoKWW91IGNhbiBhbHNvICJwaXBlIiBiZXR3ZWVuIGRwbHlyIGFuZCBnZ3Bsb3QyIGNvZGUKCmBgYHtyfQp0b3RhbF92b3RlcyAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gUGFydHksIHkgPSBWb3RlcywgZmlsbD1QYXJ0eSkpICsgZ2VvbV9jb2woKQpgYGAKSG93ZXZlciwgdGhlc2Ugc3VtbWVkIHZhbHVlcyBkb24ndCBhY3V0YWxseSB0ZWxsIHVzIHdobyBvbmUgdGhlIGVsZWN0aW9uLiBJbnN0ZWFkLCB3ZSBuZWVkIHRvIGtub3cgd2hvIHdvbiBpbiBlYWNoIGNvbnN0aXR1ZW5jeS4KCmBgYHtyfQpjbGVhbmVkICU+JSAKICBwaXZvdF9sb25nZXIoQ29uc2VydmF0aXZlOk90aGVyLCBuYW1lc190byA9ICJQYXJ0eSIsIHZhbHVlc190byA9IlZvdGVzIikgJT4lIAogIGZpbHRlcighaXMubmEoVm90ZXMpKQogIApgYGAKCkNhbGN1bGF0ZSB0aGUgcGVyY2VudGFnZSBvZiB2b3RlcyBmb3IgZWFjaCBwYXJ0eSBpbiBlYWNoIGNvbnN0aXR1ZW5jeS4KCmBgYHtyfQpjbGVhbmVkICU+JSAKICBwaXZvdF9sb25nZXIoQ29uc2VydmF0aXZlOk90aGVyLCBuYW1lc190byA9ICJQYXJ0eSIsIHZhbHVlc190byA9IlZvdGVzIikgJT4lIAogIGZpbHRlcighaXMubmEoVm90ZXMpKSAlPiUgCiAgbXV0YXRlKGBQZXJjZW50YWdlIG9mIFZvdGVgID0gKFZvdGVzIC8gYFRvdGFsIHZvdGVzYCkqMTAwKQpgYGAKV2UgY2FuIG5vdyBkcmlsbC1kb3duIGludG8gdGhlIHJlc3VsdHMgZm9yIHBhcnRpY3VsYXIgY29uc3RpdHVlbmNpZXMuIExldHMnIGxvb2sgYXQgYWxsIHRoZSBTaGVmZmllbGQgcmVzdWx0cyBmb3IgdGhlIGZvdXIgYmlnZ2VzdCBwYXJ0aWVzLiAKCmBgYHtyfQpzaGVmX3ZvdGVzIDwtIGNsZWFuZWQgJT4lIAogIHBpdm90X2xvbmdlcihDb25zZXJ2YXRpdmU6T3RoZXIsIG5hbWVzX3RvID0gIlBhcnR5IiwgdmFsdWVzX3RvID0iVm90ZXMiKSAlPiUgCiAgZmlsdGVyKCFpcy5uYShWb3RlcykpICU+JSAKICBtdXRhdGUoYFBlcmNlbnRhZ2Ugb2YgVm90ZWAgPSAoVm90ZXMgLyBgVG90YWwgdm90ZXNgKSoxMDApICU+JSAKICBmaWx0ZXIoZ3JlcGwoIlNIRUZGSUVMRCIsIENvbnN0aXR1ZW5jeSksIAogICAgICAgICBQYXJ0eSAlaW4lIGMoIkNvbnNlcnZhdGl2ZSIsICJHcmVlbiIsICJMYWJvdXIiLCAiTGliLiBEZW0uIikpIApgYGAKCi0tLS0KCioqRXhlcmNpc2UqKgoKUHJvZHVjZSB0aGUgZm9sbG93aW5nIGdyYXBoIHRvIHNob3cgdGhlIHZvdGUgc3BsaXQgZm9yIHRoZSBkaWZmZXJlbnQgcGFydHMgb2YgU2hlZmZpZWxkLiBUaGVzZSBjb2xvdXJzIHdlcmUgdXNlZCBpbiB0aGUgZXhhbXBsZSBwbG90Oi0KCmBgYHtyIGV2YWw9RkFMU0V9CmMoIiMwMDg3REMiLCAiIzAwODA2NiIsIiNEQzI0MWYiLCIjRkRCQjMwIikKYGBgCgoKIVtdKHNoZWYtdm90ZXMucG5nKQotLS0tCgpOb3cgdG8gZGV0ZXJtaW5lIHRoZSB3aW5uZXJzIGZvciBlYWNoIGNvbnN0aXR1ZW5jeS4gVG8gZG8gdGhpcywgd2Ugd2lsbCBvcmRlciB0aGUgcGVyY2VudGFnZSBvZiB2b3RlcyB3aXRoaW4gZWFjaCBjb25zdGl0dWVuY3kgYnkgZmlyc3QgZ3JvdXBpbmcgd2l0aGluIGNvbnN0aXR1ZW5jeSBhbmQgdGhlbiB1c2luZyBgYXJyYW5nZWAuCgpgYGB7cn0KY2xlYW5lZCAlPiUgCiAgcGl2b3RfbG9uZ2VyKENvbnNlcnZhdGl2ZTpPdGhlciwgbmFtZXNfdG8gPSAiUGFydHkiLCB2YWx1ZXNfdG8gPSJWb3RlcyIpICU+JSAKICBmaWx0ZXIoIWlzLm5hKFZvdGVzKSkgJT4lIAogIG11dGF0ZShgUGVyY2VudGFnZSBvZiBWb3RlYCA9IChWb3RlcyAvIGBUb3RhbCB2b3Rlc2ApKjEwMCkgJT4lIAogIGdyb3VwX2J5KENvbnN0aXR1ZW5jeSkgJT4lIAogIGFycmFuZ2UoZGVzYyhgUGVyY2VudGFnZSBvZiBWb3RlYCksLmJ5X2dyb3VwID0gVFJVRSkKYGBgClRvIGNhbGN1bGF0ZSB0aGUgbWFqb3JpdHkgKGkuZS4gaG93IG11Y2ggZWFjaCBzZWF0IHdhcyB3b24gYnkpLCB3ZSdsbCBuZWVkIHRoZSBmaXJzdCBhbmQgc2Vjb25kIHBsYWNlIHZvdGVzLiBUaGUgYGxlYWRgIGZ1bmN0aW9uIGlzIHVzZWQgdG8gZmluZCB0aGUgc2Vjb25kIGhpZ2hlc3QgbnVtYmVyIG9mIHZvdGVzLgoKYGBge3J9CmNsZWFuZWQgJT4lIAogIHBpdm90X2xvbmdlcihDb25zZXJ2YXRpdmU6T3RoZXIsIG5hbWVzX3RvID0gIlBhcnR5IiwgdmFsdWVzX3RvID0iVm90ZXMiKSAlPiUgCiAgZmlsdGVyKCFpcy5uYShWb3RlcykpICU+JSAKICBtdXRhdGUoYFBlcmNlbnRhZ2Ugb2YgVm90ZWAgPSAoVm90ZXMgLyBgVG90YWwgdm90ZXNgKSoxMDApICU+JSAKICBncm91cF9ieShDb25zdGl0dWVuY3kpICU+JSAKICBhcnJhbmdlKGRlc2MoYFBlcmNlbnRhZ2Ugb2YgVm90ZWApLC5ieV9ncm91cCA9IFRSVUUpICU+JSAKICBzbGljZSgxOjIpICU+JSAKICBtdXRhdGUoYFNlY29uZCBQbGFjZWAgPSBsZWFkKGBQZXJjZW50YWdlIG9mIFZvdGVgLG9yZGVyX2J5ID0gQ29uc3RpdHVlbmN5KSwgTWFqb3JpdHkgPSBgUGVyY2VudGFnZSBvZiBWb3RlYCAtIGBTZWNvbmQgUGxhY2VgKQpgYGAKCkZpbmFsbHksIHdlIGNhbiBzZWxlY3QgdGhlIHdpbm5lciBvZiBlYWNoIHNlYXQgYW5kIHRoZWlyIG1ham9yaXR5LgoKYGBge3J9CnJlc3VsdHMgPC0gY2xlYW5lZCAlPiUgCiAgcGl2b3RfbG9uZ2VyKENvbnNlcnZhdGl2ZTpPdGhlciwgbmFtZXNfdG8gPSAiUGFydHkiLCB2YWx1ZXNfdG8gPSJWb3RlcyIpICU+JSAKICBmaWx0ZXIoIWlzLm5hKFZvdGVzKSkgJT4lIAogIG11dGF0ZShgUGVyY2VudGFnZSBvZiBWb3RlYCA9IChWb3RlcyAvIGBUb3RhbCB2b3Rlc2ApKjEwMCkgJT4lIAogIGdyb3VwX2J5KENvbnN0aXR1ZW5jeSkgJT4lIAogIGFycmFuZ2UoZGVzYyhgUGVyY2VudGFnZSBvZiBWb3RlYCksLmJ5X2dyb3VwID0gVFJVRSkgJT4lIAogIHNsaWNlKDE6MikgJT4lIAogIG11dGF0ZShgU2Vjb25kIFBsYWNlYCA9IGxlYWQoYFBlcmNlbnRhZ2Ugb2YgVm90ZWAsb3JkZXJfYnkgPSBDb25zdGl0dWVuY3kpLCBNYWpvcml0eSA9IGBQZXJjZW50YWdlIG9mIFZvdGVgIC0gYFNlY29uZCBQbGFjZWApICU+JSAKICBzbGljZSgxKQpgYGAKCkRpZCBzb21lIHBhcnRpZXMgd2luIGJ5IHNsaW1tZXIgbWFqb3JpdGllcz8KCmBgYHtyfQpyZXN1bHRzICU+JSAKICBmaWx0ZXIoUGFydHkgJWluJSBjKCJDb25zZXJ2YXRpdmUiLCAiR3JlZW4iLCAiTGFib3VyIiwgIkxpYi4gRGVtLiIsIlNOUCIpKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gUGFydHksIHkgPSBNYWpvcml0eSxmaWxsPVBhcnR5KSkgKyBnZW9tX2JveHBsb3QoKSArIGdlb21faml0dGVyKHdpZHRoPTAuMSkgKyBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9YygiIzAwODdEQyIsICIjMDA4MDY2IiwiI0RDMjQxZiIsIiNGREJCMzAiLCIjRkZGRjAwIikpCmBgYAoKCldlIGNhbiBub3cgd29yayBvdXQgdGhlIG51bWJlciBvZiBzZWF0cwoKYGBge3J9CnNlYXRzXzIwMTkgPC0gcmVzdWx0cyAlPiUgCiAgdW5ncm91cCgpICU+JSAKICBjb3VudChQYXJ0eSkgJT4lIAogIHJlbmFtZShTZWF0cyA9IG4pCndyaXRlX2NzdihzZWF0c18yMDE5LCAiMjAxOV9zZWF0cy5jc3YiKQpgYGAKCmBgYHtyfQpzZWF0c18yMDE5ICU+JSAKICBmaWx0ZXIoUGFydHkgJWluJSBjKCJDb25zZXJ2YXRpdmUiLCAiR3JlZW4iLCAiTGFib3VyIiwgIkxpYi4gRGVtLiIsIlNOUCIpKSAlPiUgCmdncGxvdChhZXMoeCA9IGZjdF9yZW9yZGVyKFBhcnR5LCBTZWF0cykseSA9IFNlYXRzLGZpbGw9UGFydHkpKSArIGdlb21fY29sKCkgKyBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9YygiIzAwODdEQyIsICIjMDA4MDY2IiwiI0RDMjQxZiIsIiNGREJCMzAiLCIjRkZGRjAwIikpICsgeGxhYigiUGFydHkiKSArIHlsYWIoIk51bWJlciBvZiBTZWF0cyIpCmBgYAoK